﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace LogicalComponent.util
{

    public enum SquareDirection
    {
        NORTH, WEST, SOUTH, EAST, NORTHWEST, SOUTHWEST, SOUTHEAST, NORTHEAST
    }

    public enum HexDirection
    {
        NORTH, SOUTH, NORTHWEST, SOUTHWEST, SOUTHEAST, NORTHEAST
    }

    /// <summary>
    /// A class for keeping track of descrete coordinates on a grid
    /// </summary>
    [Serializable()]
    public class Coordinate : ISerializable
    {

        // *********************************************************************
        // **************** STATIC *********************************************
        // *********************************************************************

        /// <summary>
        /// Dictionary of the vectors relating to the different directions on a Square Gird 
        /// </summary>
        public static Dictionary<SquareDirection, Coordinate> SquareDirections;
        /// <summary>
        /// Dictionary of the vectors relating to the different directions on a Hex Gird 
        /// </summary>
        public static Dictionary<HexDirection, Coordinate> HexDirections;

        /// <summary>
        /// Initializes the <see cref="Coordinate"/> class.
        /// </summary>
        static Coordinate()
        {
            SquareDirections = new Dictionary<SquareDirection,Coordinate>();
            SquareDirections.Add(SquareDirection.NORTH, new Coordinate(0, -1));
            SquareDirections.Add(SquareDirection.WEST, new Coordinate(-1, 0));
            SquareDirections.Add(SquareDirection.SOUTH, new Coordinate(0, 1));
            SquareDirections.Add(SquareDirection.EAST, new Coordinate(1, 0));
            SquareDirections.Add(SquareDirection.NORTHWEST, new Coordinate(-1, -1));
            SquareDirections.Add(SquareDirection.SOUTHWEST, new Coordinate(-1, 1));
            SquareDirections.Add(SquareDirection.SOUTHEAST, new Coordinate(1, 1));
            SquareDirections.Add(SquareDirection.NORTHEAST, new Coordinate(1, -1));

            HexDirections = new Dictionary<HexDirection,Coordinate>();
            HexDirections.Add(HexDirection.NORTH, new Coordinate(0, -1));
            HexDirections.Add(HexDirection.NORTHWEST, new Coordinate(-1, 0));
            HexDirections.Add(HexDirection.SOUTHWEST, new Coordinate(-1, 1));
            HexDirections.Add(HexDirection.SOUTH, new Coordinate(0, 1));
            HexDirections.Add(HexDirection.SOUTHEAST, new Coordinate(1, 0));
            HexDirections.Add(HexDirection.NORTHEAST, new Coordinate(1, -1));
        }

        // *********************************************************************
        // **************** ATTRIBUTES *****************************************
        // *********************************************************************

        /// <summary>
        /// Gets or sets the X.
        /// </summary>
        /// <value>
        /// The X.
        /// </value>
        public int X;
        /// <summary>
        /// Gets or sets the Z.
        /// </summary>
        /// <value>
        /// The Z.
        /// </value>
        public int Z;

        public Coordinate North { get { return this + SquareDirections[SquareDirection.NORTH]; } }
        public Coordinate West { get { return this + SquareDirections[SquareDirection.WEST]; } }
        public Coordinate South { get { return this + SquareDirections[SquareDirection.SOUTH]; } }
        public Coordinate East { get { return this + SquareDirections[SquareDirection.EAST]; } }
        public Coordinate NorthWest { get { return this + SquareDirections[SquareDirection.NORTHWEST]; } }
        public Coordinate SouthWest { get { return this + SquareDirections[SquareDirection.SOUTHWEST]; } }
        public Coordinate SouthEast { get { return this + SquareDirections[SquareDirection.SOUTHEAST]; } }
        public Coordinate NorthEast { get { return this + SquareDirections[SquareDirection.NORTHEAST]; } }

        public Coordinate HexNorth { get { return this + HexDirections[HexDirection.NORTH]; } }
        public Coordinate HexNorthWest { get { return this + HexDirections[HexDirection.NORTHWEST]; } }
        public Coordinate HexSouthWest { get { return this + HexDirections[HexDirection.SOUTHWEST]; } }
        public Coordinate HexSouth { get { return this + HexDirections[HexDirection.SOUTH] ; } }
        public Coordinate HexSouthEast { get { return this + HexDirections[HexDirection.SOUTHEAST] ; } }
        public Coordinate HexNorthEast { get { return this + HexDirections[HexDirection.NORTHEAST]; } }

        /// <summary>
        /// Gets the magnitude.
        /// </summary>
        public double magnitude { get { return Math.Sqrt(X * X + Z * Z); } }

        // *********************************************************************
        // **************** CONSTRUCTOR ****************************************
        // *********************************************************************

        /// <summary>
        /// Initializes a new instance of the <see cref="Coordinate"/> class.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="z">The z.</param>
        public Coordinate(int x, int z)
        {
            this.X = x;
            this.Z = z;
        }

        // *********************************************************************
        // **************** OPERATORS ******************************************
        // *********************************************************************

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("({0}, {1})", this.X, this.Z);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
        /// </returns>
        public override int GetHashCode()
        {
            int r = 1;
            for (int i = 0; i < this.Z; i++)
            {
                r *= this.X;
            }
            return r;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="o">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override Boolean Equals(object o)
        {
            if (o == null)
            {
                return false;
            }

            Coordinate c = o as Coordinate;
            if ((System.Object)c == null)
            {
                return false;
            }

            return this.X == c.X && this.Z == c.Z;
        }

        /// <summary>
        /// Equalses the specified other.
        /// </summary>
        /// <param name="other">The other.</param>
        /// <returns></returns>
        public Boolean Equals(Coordinate other)
        {
            if (other == null)
            {
                return false;
            }

            return this.X == other.X && this.Z == other.Z;
        }

        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="destination">The destination.</param>
        /// <param name="source">The source.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Coordinate operator -(Coordinate destination, Coordinate source)
        {
            return new Coordinate(destination.X - source.X, destination.Z - source.Z);
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="vector1">The vector1.</param>
        /// <param name="vector2">The vector2.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Coordinate operator +(Coordinate vector1, Coordinate vector2)
        {
            return new Coordinate(vector1.X + vector2.X, vector1.Z + vector2.Z);
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator ==(Coordinate first, Coordinate second)
        {
            if (System.Object.ReferenceEquals(first, second))
            {
                return true;
            }

            if ((object)first == null || (object)second == null)
            {
                return false;
            }

            return first.X == second.X && first.Z == second.Z;
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator !=(Coordinate first, Coordinate second)
        {
            return !(first == second);
        }

        // *********************************************************************
        // **************** PUBLIC *********************************************
        // *********************************************************************

        /// <summary>
        /// Gets the distance to.
        /// </summary>
        /// <param name="other">The other.</param>
        /// <returns></returns>
        public double GetDistanceToSquare(Coordinate other)
        {
            return (other - this).magnitude;
        }

        // *********************************************************************
        // **************** SERIALIZATION **************************************
        // *********************************************************************

        /// <summary>
        /// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data.</param>
        /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization.</param>
        /// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. </exception>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("X", this.X, typeof(int));
            info.AddValue("Z", this.Z, typeof(int));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Coordinate"/> class.
        /// </summary>
        /// <param name="info">The info.</param>
        /// <param name="context">The context.</param>
        public Coordinate(SerializationInfo info, StreamingContext context)
        {
            this.X = (int) info.GetValue("X", typeof(int));
            this.Z = (int) info.GetValue("Z", typeof(int));
        }
    }
}
